﻿namespace UsefulCode
{
	using System;
	using System.Configuration;
	using System.Data.Common;
	using System.Diagnostics;
	using System.Diagnostics.CodeAnalysis;
	using System.Diagnostics.Contracts;
	using System.Collections.Generic;
	using System.Web.Hosting;

	[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Db")]
	public static class Db
	{
		[DebuggerNonUserCode]
		public static string GetProviderName(string connectionName)
		{
			Contract.Requires(!string.IsNullOrEmpty(connectionName));

			return ConfigurationManager.ConnectionStrings[connectionName].ProviderName;
		}

		[DebuggerNonUserCode]
		public static string GetConnectioString(string connectionName)
		{
			string connectionString = ConfigurationManager.ConnectionStrings[connectionName].ConnectionString;

			//if (connectionString.Contains("|DataDirectory|"))
			//{
			//    connectionString = connectionString.Replace("|DataDirectory|", HostingEnvironment.MapPath("~/App_Data") + "\\");
			//}

			return connectionString;
		}

		[DebuggerNonUserCode]
		public static void Work(string connectionName, params Action<DbCommand>[] invokeCommands)
		{
			Contract.Requires(!string.IsNullOrEmpty(connectionName));

			string providerName = GetProviderName(connectionName);
			string connectionString = ConfigurationManager.ConnectionStrings[connectionName].ConnectionString;

			if(connectionString.Contains("|DataDirectory|")) {
			connectionString = connectionString.Replace("|DataDirectory|",  HostingEnvironment.MapPath("~/App_Data") + "\\");
			}

			Work(connectionString, providerName, invokeCommands);
		}

		[DebuggerNonUserCode]
		public static IEnumerable<T> ReadAndCast<T>(string connectionName, Func<DbCommand, IEnumerable<T>> cast)
		{
			Contract.Requires(!string.IsNullOrEmpty(connectionName));

			string providerName = GetProviderName(connectionName);
			string connectionString = ConfigurationManager.ConnectionStrings[connectionName].ConnectionString;

			return ReadAndCast(connectionString, providerName, cast);
		}


		[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0")]
		public static void StartTransaction(this DbCommand command)
		{
			Contract.Requires(command != null);

			DbConnection connection = command.Connection;

			if (connection == null || connection.State != System.Data.ConnectionState.Open)
			{
				throw new ArgumentException("No connection object found for the command.");
			}

			command.Transaction = connection.BeginTransaction();
		}

		[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "2"), DebuggerNonUserCode]
		public static IEnumerable<T>  ReadAndCast<T>(string connectionString, string providerName, Func<DbCommand, IEnumerable<T>> cast)
		{

			Contract.Requires(!string.IsNullOrWhiteSpace(connectionString));
			Contract.Requires(!string.IsNullOrEmpty(connectionString));
			Contract.Requires(!string.IsNullOrEmpty(providerName));
			Contract.Requires(!string.IsNullOrWhiteSpace(providerName));
			Contract.Requires(cast != null);

			DbProviderFactory factory = DbProviderFactories.GetFactory(providerName);

			using (DbConnection connection = factory.CreateConnection())
			{
				connection.ConnectionString = connectionString;
				connection.Open();

				using (DbCommand command = connection.CreateCommand())
				{
					foreach(var casted in cast(command))
					yield return casted;
				}
			}
		}



		[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "2"), DebuggerNonUserCode]
		public static void Work(string connectionString, string providerName, params Action<DbCommand>[] invokeCommands)
		{

			Contract.Requires(!string.IsNullOrWhiteSpace(connectionString));
			Contract.Requires(!string.IsNullOrEmpty(connectionString));
			Contract.Requires(!string.IsNullOrEmpty(providerName));
			Contract.Requires(!string.IsNullOrWhiteSpace(providerName));
			Contract.Requires(invokeCommands != null);

			DbProviderFactory factory = DbProviderFactories.GetFactory(providerName);

			using (DbConnection connection = factory.CreateConnection())
			{
				connection.ConnectionString = connectionString;
				connection.Open();

				using (DbCommand command = connection.CreateCommand())
				{

					foreach (Action<DbCommand> invokeCommand in invokeCommands)
					{
						invokeCommand(command);
					}

					if (command.Transaction != null)
					{
						command.Transaction.Commit();
					}
				}
			}
		}

		[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "2"), DebuggerNonUserCode]
		public static void Work(string connectionName, bool useTransaction, params Action<DbCommand>[] invokeCommands)
		{

			Contract.Requires(!string.IsNullOrEmpty(connectionName));
			Contract.Requires(!string.IsNullOrWhiteSpace(connectionName));
			Contract.Requires(invokeCommands != null);

			if (!useTransaction)
				Db.Work(connectionName, invokeCommands);

			DbProviderFactory factory = DbProviderFactories.GetFactory(ConfigurationManager.ConnectionStrings[connectionName].ProviderName);
			DbTransaction transaction = null;

			using (DbConnection connection = factory.CreateConnection())
			{
				connection.ConnectionString = ConfigurationManager.ConnectionStrings[connectionName].ConnectionString;
				connection.Open();

				transaction = connection.BeginTransaction();

				using (DbCommand command = connection.CreateCommand())
				{
					command.Transaction = transaction;

					foreach (Action<DbCommand> invokeCommand in invokeCommands)
					{
						invokeCommand(command);
					}
				}

				transaction.Commit();
			}
		}

		[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "2"), DebuggerNonUserCode]
		public static void CreateParameter<T>(string name, T parameterValue, DbCommand command)
		{
			Contract.Requires(!string.IsNullOrEmpty(name));
			Contract.Requires(command != null);

			DbParameter dbParameter;
			dbParameter = command.CreateParameter();
			dbParameter.ParameterName = name;
			dbParameter.Value = parameterValue;
			command.Parameters.Add(dbParameter);
		}

		[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0")]
		public static void CreateParameter<T>(this DbCommand command, string name, T parameterValue)
		{
			Contract.Requires<ArgumentNullException>(command != null);
			Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(name));

			DbParameter dbParameter;
			dbParameter = command.CreateParameter();
			dbParameter.ParameterName = name;
			dbParameter.Value = parameterValue;
			command.Parameters.Add(dbParameter);
		}

		[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0"), DebuggerNonUserCode]
		public static int ExecuteAndGetRowId(DbCommand command)
		{
			Contract.Requires(command != null);

			object something = command.ExecuteScalar();

			if (something == null)
			{
				return -1;
			}
			else
			{
				return int.Parse(something.ToString());
			}
		}

		[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0"), DebuggerNonUserCode]
		public static int ExecuteNonQuery(DbCommand command)
		{
			Contract.Requires(command != null);

			return command.ExecuteNonQuery();
		}
	}
}